package gapidt.analysis;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;

public class ASTUtils {
	
	protected static Map<IJavaElement, ASTNode> cache = new HashMap<IJavaElement, ASTNode>();
	
	
	public static CompilationUnit parse(ICompilationUnit iCompilationUnit){
		if(cache.get(iCompilationUnit) != null)
			return (CompilationUnit) cache.get(iCompilationUnit);
		else{
			if(iCompilationUnit != null){
				ASTParser parser = ASTParser.newParser(AST.JLS3);
				parser.setKind(ASTParser.K_COMPILATION_UNIT);
				parser.setSource(iCompilationUnit);
				parser.setResolveBindings(true);
				CompilationUnit compilationUnit = (CompilationUnit) parser.createAST(null);
				cache.put(iCompilationUnit, compilationUnit);
				
				TypesVisitor typesVisitor = new TypesVisitor();
				compilationUnit.accept(typesVisitor);
				for(TypeDeclaration type : typesVisitor.getTypes())
					cache.put(type.resolveBinding().getJavaElement(), type);
				
				return compilationUnit;
			}
		}
		return null;
	}
	
	public static TypeDeclaration parse(IType type){
		if(cache.get(type) != null)
			return (TypeDeclaration) cache.get(type);
		else{
			if(type != null){
				parse(type.getCompilationUnit());
				return (TypeDeclaration) cache.get(type);
			}
		}
		return null;
	}
	
	public static MethodDeclaration parse(IMethod method){
		if(cache.get(method) != null)
			return (MethodDeclaration) cache.get(method);
		else{
			if(method != null){
				TypeDeclaration typeDeclaration = parse(method.getDeclaringType());
				if(typeDeclaration != null){
					for(MethodDeclaration methodDeclaration : typeDeclaration.getMethods()){
						if(areEqual(method, methodDeclaration))
							return methodDeclaration;
					}
				}
			}
		}
		return null;
	}
	
	public static FieldDeclaration parse(IField field){
		if(cache.get(field) != null)
			return (FieldDeclaration) cache.get(field);
		else{
			if(field != null){
				TypeDeclaration typeDeclaration = parse(field.getDeclaringType());
				if(typeDeclaration != null){
					for(FieldDeclaration fieldDeclaration : typeDeclaration.getFields()){
						if(areEqual(field, fieldDeclaration))
							return fieldDeclaration;
					}
				}
			}
		}
		return null;
	}
	
	public static boolean areEqual(IType type, TypeDeclaration typeDeclaration){
		return type.getElementName().equals(typeDeclaration.getName().toString());
	}

	public static boolean areEqual(IMethod method, MethodDeclaration methodDeclaration){
		String name1 = getUniqueName(method);
		String name2 = getUniqueName(methodDeclaration);
		
		if(!name1.isEmpty() && !name2.isEmpty())
			return name1.equals(name2);
		return false;
	}
	
	public static boolean areEqual(IField field, FieldDeclaration fieldDeclaration){
		try {
			return field.getSourceRange().getOffset() == fieldDeclaration.getStartPosition();
		} catch (JavaModelException e) {
			return false;
		}
	}
	public static void clearCache(){
		cache.clear();
	}
	
	public static String getMethodComments(IMethod method){
		return getMethodComments(parse(method));
	}
	
	public static String getMethodComments(MethodDeclaration method){
		String comments = "";
		if(method != null && method.getJavadoc() != null){
			for(Object tag : method.getJavadoc().tags())
				if(tag.toString().indexOf('@') < 0)
					comments += tag.toString().replace('*', ' ').trim();
		}
		return comments.trim();	
	}
	
	public static String getMethodParametersComments(IMethod method){
		return getMethodParametersComments(parse(method));
	}
	
	public static String getMethodParametersComments(MethodDeclaration method){
		String parametersComments = "";
		Pattern parameterCommentsPattern = Pattern.compile("\\@param\\s+([^\\s]+)\\s+(.*)");
		
		if(method != null && method.getJavadoc() != null){
			for(Object tag : method.getJavadoc().tags()){
				Matcher matcher = parameterCommentsPattern.matcher(tag.toString().replace('*', ' ').trim());
				if(matcher.find()){
					String desc = matcher.group(2).trim();
					if(!desc.endsWith("."))
						desc += ".";
					parametersComments += matcher.group(1)+": "+desc+"\n";
				}
			}
		}
		return parametersComments.trim();	
	}
	
	public static String getMethodReturnComments(IMethod method){
		return getMethodReturnComments(parse(method));
	}
	
	public static String getMethodReturnComments(MethodDeclaration method){
		Pattern returnCommentsPattern = Pattern.compile("\\@return\\s+(.*)");
		if(method != null && method.getJavadoc() != null){
			for(Object tag : method.getJavadoc().tags()){
				Matcher matcher = returnCommentsPattern.matcher(tag.toString().replace('*', ' ').trim());
				if(matcher.find()){
					return matcher.group(1);
				}
			}
		}
		return "";	
	}
	
	public static String getTypeComments(IType type){
		return getTypeComments(parse(type));
	}
	
	public static String getTypeComments(TypeDeclaration type){
		String comments = "";
		if(type != null && type.getJavadoc() != null){
			for(Object tag : type.getJavadoc().tags())
				comments += tag.toString().replace('*', ' ').trim()+"\n";
		}
		return comments.trim();	
	}
	
	public static String getClassName(IMethod method){
		if(method != null && method.getParent() instanceof IType)
			return method.getParent().getElementName();
		return "";
	}
	
	public static String getClassName(MethodDeclaration method){
		String className = "";
		if(method != null && method.getParent().getClass().isAssignableFrom(TypeDeclaration.class))
			className = ((TypeDeclaration) method.getParent()).getName().toString();
		return className;
	}
	
	public static boolean hasModifier(IMethod method, String modifier){
		return hasModifier(parse(method), modifier);
	}
	
	@SuppressWarnings("unchecked")
	public static boolean hasModifier(MethodDeclaration method, String modifier){
		if(method != null){
			List<ASTNode> childProperties = (List<ASTNode>) method.getStructuralProperty(MethodDeclaration.MODIFIERS2_PROPERTY); 
			for(ASTNode childNode: childProperties) 
				if(childNode.toString().toLowerCase().equals(modifier.toLowerCase())) 
					return true;
		}
		return false;
	}
	
	public static boolean hasNoModifiers(IMethod method){
		return hasNoModifiers(parse(method));
	}
	
	@SuppressWarnings("unchecked")
	public static boolean hasNoModifiers(MethodDeclaration method){
		if(method != null){
			List<ASTNode> childProperties = (List<ASTNode>) method.getStructuralProperty(MethodDeclaration.MODIFIERS2_PROPERTY);
			return childProperties.size() == 0;
		}
		return true;
	}
	
	public static boolean hasModifier(IField field, String modifier){
		return hasModifier(parse(field), modifier);
	}
	
	public static boolean isPublic(IField field){
		return isPublic(parse(field));
	}
	
	public static boolean isPublic(FieldDeclaration fieldDeclaration){
		return hasModifier(fieldDeclaration, "public");
	}
	
	@SuppressWarnings("unchecked")
	public static boolean hasModifier(FieldDeclaration field, String modifier){
		if(field != null){
			List<ASTNode> childProperties = (List<ASTNode>) field.getStructuralProperty(FieldDeclaration.MODIFIERS2_PROPERTY); 
			for(ASTNode childNode: childProperties) 
				if(childNode.toString().toLowerCase().equals(modifier.toLowerCase())) 
					return true;
		}
		return false;
	}
	
	public static boolean isPublic(IMethod method){
		return isPublic(parse(method));
	}
	
	public static boolean isPublic(MethodDeclaration method){
		return hasModifier(method, "public") ||
				(method != null && method.getParent() instanceof TypeDeclaration && ((TypeDeclaration) method.getParent()).isInterface() && hasNoModifiers(method));
	}
	
	public static String getUniqueName(IMethod method){
		if(method == null)
			return "";
		String name = getClassName(method);
		name += "."+method.getElementName();
		name += "-"+method.getParameterTypes().length;
		return name;
	}
	
	public static String getUniqueName(MethodDeclaration method){
		if(method == null)
			return "";
		String name = getClassName(method);
		name += "."+method.getName().toString();
		name += "-"+method.parameters().size();
		return name;
	}
	
	public static class TypesVisitor extends ASTVisitor{
		
		protected List<TypeDeclaration> types = new ArrayList<TypeDeclaration>();
		
		@Override
		public boolean visit(TypeDeclaration node){
			types.add(node);
			return super.visit(node);
		}
		
		public List<TypeDeclaration> getTypes(){
			return types;
		}
	}
}
